package chapter7;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.Function;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class Note {
    /**
     * 第7章 并行数据处理与性能
     *  本章内容：
     *      用并行流并行处理数据
     *      并行流的性能分析
     *      分支/合并框架
     *      使用Spliterator分割流
     *
     *  7.1 并行流
     *      通过对收集源调用parallelStream方法来把集合转换为并行流
     *      并行流就是一个把内容分成多个数据块，并用不同的线程分别处理每个数据块的流
     */
    public static void main(String[] args) {
//        System.out.println("Sequential sum done in:" + measureSumPerf(ParallelStreams::sequentialSum,10_000_000) + " msecs");
//        System.out.println("Iterative sum done in:" + measureSumPerf(ParallelStreams::iterativeSum,10_000_000) + " msecs");
//        System.out.println("Parallel sum done in:" + measureSumPerf(ParallelStreams::parallelSum, 10_000_000) + " msecs");
//        System.out.println("Ranged sum done in:" + measureSumPerf(ParallelStreams::rangeSum,10_000_000) + " msecs");
//        System.out.println("SideEffect parallel sum done in:" + measureSumPerf(ParallelStreams::sideEffectSum,10_000_000) + " msecs");
        System.out.println("ForkJoin sum done in:" + measureSumPerf(ForkJoinSumCalculator::forkJoinSum, 10_000_000) + " msecs");
        System.out.println("你电脑的可用处理器数：" + Runtime.getRuntime().availableProcessors());
    }
    /**
     * 将顺序流转换为并行流
     *  通过调用parallel()
     *
     * 相反 调用sequential方法可以把它变成顺序流
     *
     * 注意 如果同时调用了 parallel() 和 sequential 那么 之后最后一次调用的哪个才起作用
     *
     * 并行流用的线程从哪儿来？有多少个？怎么自定义这个过程？
     *      并行流内部使用默认的ForkJoinPool，它默认的线程数量就是你处理器数量 这个值由Runtime.getRuntime().availableProcessors()得到
     *      可以修改  但是不建议 这样做
     */

    /**
     * 测量流性能
     *  看起来并行流求和方法应该比顺序和迭代方法性能好。然而在软件工程上，靠猜绝对不是什么好办法！  特别是在性能优化时  我们应该遵循3个黄金原则：测量、测量、再测量
     */

    /**
     * 测量对前n个自然数求和的函数性能
     * @param addr
     * @param n
     * @return
     *
     * 对比结果是 迭代求和 >(快于) 顺序求和 >(快于) 并行求和
     *  这个结果 使我们很难理解 为什么 并行求和是最慢的
     *      实际上有2个问题：
     *          1、iterate生成的是装箱对象  必须拆箱成数字才能求和
     *          2、我们很难把iterate分成多个独立块来并行执行 因此每次应用函数都要依赖前一次应用的结果
     *   解决方式：
     *      使用LongStream.rangeClosed的方法 这个方法与iterate相比有2个优点：
     *          1、LongStream.rangeClosed直接产生原始类型的long数字 没有装箱和拆箱的开销
     *          2、LongStream.rangeClosed会产生数字范围 很容易拆分为独立的小块例如范围1~20
     *   测试结果显示 比Stream.iterate工厂方法生成数字的顺序执行版本要快多了  因此 选择合适的数据结构往往比并行化算法更重要
     *   如果再加上 并行执行  那么 测试结果 显示 这是执行最快的求和方法
     *   因此 测试表明： 使用正确的数据结构然后使其并行工作能够保证最佳的性能
     *   但是并行也是有代价的 数据需要在不同的核处理器移动
     *   总而言之，很多情况下不可能或不方便并行化
     */
    public static long measureSumPerf(Function<Long, Long> addr, long n){
        long fastest = Long.MAX_VALUE;
        for (int i = 0; i < 10; i++){
            long start = System.nanoTime();
            long sum = addr.apply(n);
            long duration = (System.nanoTime() - start) / 1_000_000;
            System.out.println("Result: " + sum);
            if (duration < fastest) fastest = duration;
        }
        return fastest;
    }

    /**
     * 高效使用并行流
     *      建议：
     *          1、如果有疑问，测量。
     *          2、留意装箱 使用Java 8中的IntStream LongStream DoubleStream来避免这种操作
     *          3、有些操作本身在并行流上的性能就比顺序流差  特别是limit和findFirst等依赖于元素顺序的操作
     *          4、还有考虑流的操作流水线的总计算成本
     *          5、对于较小的数据量  选择并行流似乎从来都不是一个好的决定
     *          6、要考虑流背后的数据结构是否易于分解
     *          7、流自身的特点，以及流水线中的中间操作修改流的方式都可能会改变分解过程的性能
     *          8、还要考虑终端操作中合并步骤的代价是大是小
     */

    /**
     * 分支/合并框架
     *  合并/分支框架的目的是以递归方式将可以并行的任务拆分为更小的任务  然后将每个子任务的结果合并起来生成整体结果 它是ExecutorService接口的一个实现
     *  它把子任务分配给线程池(称为ForkJoinPool)中的工作线程
     *
     *  使用RecursiveTask
     *      要把任务提交到这个池，必须创建RecursiveTask<R>的一个子类  其中R是并行化任务(以及所有子任务)产生的结果类型 或者如果任务不返回结果 则是RecursiveAction类型
     *      要定义RecursiveTask 只需要实现它的唯一的抽象方法compute:
     *          protected abstract R compute();
     *       该方法同时定义了将任务拆分成子任务的逻辑 以及无法再拆分或不方便再拆分时  生成单个子任务结果的逻辑
     *       if(任务足够小或不可划分){
     *           顺序计算该任务
     *       }else{
     *           将该任务分成两个子任务
     *           递归调用本方法 拆分每个子任务  等待所有子任务完成
     *           合并每个子任务的结果
     *       }
     */

    /**
     * 使用分支/合并框架的最佳做法
     *      以下几个有效使用它的最佳做法：
     *          1、对一个任务调用join方法会阻塞调用方，直到该任务作出结果。
     */

    /**
     * Spliterator
     *  Spliterator是Java 8中加入的另一个新接口：代表"可分割的迭代器"
     */






}
